package ie.lawlor.amvc.config;

import ie.lawlor.amvc.ChildTriad;
import ie.lawlor.amvc.DialogChildTriad;
import ie.lawlor.amvc.controller.EmitToChildEvent;
import ie.lawlor.amvc.controller.EmitToParentEvent;
import ie.lawlor.amvc.controller.EmitToTriadEvent;
import ie.lawlor.amvc.controller.RouteToChildEvent;
import ie.lawlor.amvc.controller.RouteToParentEvent;
import ie.lawlor.amvc.controller.TerminateEvent;
import ie.lawlor.amvc.event.EventPayloadProxyFactory;
import ie.lawlor.amvc.event.EventPayloadProxyMappingMapImpl;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ChildBeanDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

abstract class AbstractTriadDefinitionParser implements BeanDefinitionParser {
	private static final String VIEWPARENT = "viewParent";

	private static final String VIEWNAME = "viewName";

	public static final String ID = "id";
	public static final String APPLICATION_TYPE = "application";

	public static final String TRIAD = "triad";

	public static final String APP_EVENT = "appEvent";

	private static final String TYPE = "type";

	static final String NAME = "name";

	static final String NAMESPACE = "nameSpace";

	static final String APPLICATION_NAMESPACE = "Application";

	List applicationEventBeans = new ArrayList();

	List childrenBeans = new ArrayList();

	List views = new ArrayList();

	BeanDefinition view;


	public BeanDefinition parse(Element element, ParserContext parserContext) {
		applicationEventBeans.clear();
		childrenBeans.clear();
		views.clear();
		view = null;
		BeanDefinitionRegistry registry = parserContext.getRegistry();
		// Create Triad itself
		ChildBeanDefinition triadBeanDefinition = createAndRegisterTriad(
				element, registry);
		// Then create its children
		NodeList childNodes = element.getChildNodes();
		for (int i = 0; i < childNodes.getLength(); ++i) {
			Node childNode = childNodes.item(i);
			createChildBeanDefinition(childNode, parserContext);
		}
		// Then set the properties of the triad.
		MutablePropertyValues propertyValues = triadBeanDefinition.getPropertyValues();
		if (!views.isEmpty()) {
			propertyValues.addPropertyValue("views",
					createManagedList(views));
		}
		if (view != null) {
			propertyValues.addPropertyValue("view", view);
		}
		propertyValues.addPropertyValue("applicationEvents",
				createManagedList(applicationEventBeans));
		propertyValues.addPropertyValue("children",
				createManagedList(childrenBeans));
		triadBeanDefinition.setPropertyValues(propertyValues);
		return triadBeanDefinition;
	}

	protected abstract ChildBeanDefinition createAndRegisterTriad(
			Element element, BeanDefinitionRegistry registry);

	protected ChildBeanDefinition createTriadBeanDefinition(Element element) {
		ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition(
				element.getAttribute(TYPE));
		MutablePropertyValues propertyValues = childBeanDefinition
		.getPropertyValues();
		if (element.hasAttribute(ID)) {
			childBeanDefinition.setAttribute(ID, element
					.getAttribute(ID));
			propertyValues.addPropertyValue(NAME, element
					.getAttribute(ID));
		}
//		if (element.hasAttribute(NAME)) {
//			propertyValues.addPropertyValue(NAME, element
//					.getAttribute(NAME));
//		}
		if (element.hasAttribute(VIEWNAME)
				&& element.hasAttribute(VIEWPARENT)) {
			ChildBeanDefinition viewBeanDefinition = new ChildBeanDefinition(
					element.getAttribute(VIEWPARENT));
			MutablePropertyValues viewPropertyValues = viewBeanDefinition
			.getPropertyValues();
			viewPropertyValues.addPropertyValue(NAME, element
					.getAttribute(VIEWNAME));
			viewBeanDefinition.setPropertyValues(viewPropertyValues);
			view = viewBeanDefinition;
		}
		childBeanDefinition.setPropertyValues(propertyValues);
		return childBeanDefinition;
	}

	private List createManagedList(List beanList) {
		ManagedList managedList = new ManagedList(beanList.size());
		managedList.addAll(beanList);
		return managedList;
	}

	private void createChildBeanDefinition(Node node, ParserContext parserContext) {
		if ("terminate".equals(node.getLocalName())) {
			applicationEventBeans.add(createTerminateEvent(node));
		} else if ("emitToTriad".equals(node.getLocalName())) {
			applicationEventBeans.add(createEmitToTriadEvent(node));
		} else if ("emitToChild".equals(node.getLocalName())) {
			applicationEventBeans.add(createEmitToChildEvent(node));
		} else if ("routeToChild".equals(node.getLocalName())) {
			applicationEventBeans.add(createRouteToChildEvent(node));
		} else if ("emitToParent".equals(node.getLocalName())) {
			applicationEventBeans.add(createEmitToParentEvent(node));
		} else if ("routeToParent".equals(node.getLocalName())) {
			applicationEventBeans.add(createRouteToParentEvent(node));
		} else if ("viewname".equals(node.getLocalName())) {
			views.add(node.getAttributes().getNamedItem(NAME).getNodeValue());
		} else if ("child".equals(node.getLocalName())) {
			childrenBeans.add(createChildTriad(node, parserContext));
		} else if ("dialogChild".equals(node.getLocalName())) {
			childrenBeans.add(createDialogChildTriad(node, parserContext));
		}

	}

	private BeanDefinition createTerminateEvent(Node node) {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(
				TerminateEvent.class);
		MutablePropertyValues propertyValues = updateProperties(node,
				beanDefinition);
		int noOfChildren = node.getChildNodes().getLength();
		for (int i = 0; i < noOfChildren; ++i) {
			Node childNode = node.getChildNodes().item(i);
			if (childNode != null
					&& "payload".equals(childNode.getLocalName())) {
				String payloadValue = childNode.getFirstChild()
				.getNodeValue();
				propertyValues.addPropertyValue("payload", payloadValue);
			} else if (childNode != null
					&& "payloadMapper".equals(childNode.getLocalName())) {
				EventPayloadProxyFactory payloadFactory = createPayloadProxyFactory(childNode);
				propertyValues.addPropertyValue("payloadFactory", payloadFactory);
			}
		}
		beanDefinition.setPropertyValues(propertyValues);
		return beanDefinition;
	}

	private EventPayloadProxyFactory createPayloadProxyFactory(Node node){
		Node attr = null;
		String targetClass = null;
		EventPayloadProxyMappingMapImpl mapping = new EventPayloadProxyMappingMapImpl();
		if ((attr = node.getAttributes()
				.getNamedItem("targetClass")) != null) {
			targetClass = attr.getNodeValue();
		}
		for (int i = 0; i < node.getChildNodes().getLength(); ++i) {
			Node childNode = node.getChildNodes().item(i);
			if (childNode != null && "map".equals(childNode.getLocalName())){
				Node sourceAttr = childNode.getAttributes().getNamedItem("sourceField");
				Node targetAttr = childNode.getAttributes().getNamedItem("targetField");
				mapping.addMapping(sourceAttr.getNodeValue(), targetAttr.getNodeValue());
			}
		}
		try {
			return new EventPayloadProxyFactory(Class.forName(targetClass), mapping);
		} catch (ClassNotFoundException e){
			throw new IllegalStateException("No such class found: " + e.getMessage() + " - " + targetClass);
		}
	}

	private BeanDefinition createEmitToTriadEvent(Node node) {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(
				EmitToTriadEvent.class);
		MutablePropertyValues propertyValues = updateProperties(node,
				beanDefinition);
		beanDefinition.setPropertyValues(propertyValues);
		return beanDefinition;
	}

	private BeanDefinition createEmitToChildEvent(Node node) {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(
				EmitToChildEvent.class);
		MutablePropertyValues propertyValues = updateProperties(node,
				beanDefinition);
		beanDefinition.setPropertyValues(propertyValues);
		return beanDefinition;
	}

	private BeanDefinition createRouteToChildEvent(Node node) {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(
				RouteToChildEvent.class);
		MutablePropertyValues propertyValues = updateProperties(node,
				beanDefinition);
		beanDefinition.setPropertyValues(propertyValues);
		return beanDefinition;
	}

	private BeanDefinition createEmitToParentEvent(Node node) {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(
				EmitToParentEvent.class);
		MutablePropertyValues propertyValues = updateProperties(node,
				beanDefinition);
		beanDefinition.setPropertyValues(propertyValues);
		return beanDefinition;
	}

	private BeanDefinition createRouteToParentEvent(Node node) {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(
				RouteToParentEvent.class);
		MutablePropertyValues propertyValues = updateProperties(node,
				beanDefinition);
		beanDefinition.setPropertyValues(propertyValues);
		return beanDefinition;
	}

	private BeanDefinition createChildTriad(Node node, ParserContext parserContext) {
		BeanDefinition childTriadBean = parseChildTriad(node, parserContext);
		RootBeanDefinition beanDefinition = new RootBeanDefinition(ChildTriad.class);
		MutablePropertyValues propertyValues = updateProperties(node, beanDefinition);
		beanDefinition.setPropertyValues(propertyValues);
		if (childTriadBean != null){
			propertyValues.addPropertyValue("child",
					new RuntimeBeanReference((String)childTriadBean.getAttribute("id")));
		}
		return beanDefinition;
	}


	private BeanDefinition createDialogChildTriad(Node node, ParserContext parserContext) {
		BeanDefinition childTriadBean = parseChildTriad(node, parserContext);
		RootBeanDefinition beanDefinition = new RootBeanDefinition(
				DialogChildTriad.class);
		MutablePropertyValues propertyValues = updateProperties(node,
				beanDefinition);
		beanDefinition.setPropertyValues(propertyValues);
		if (childTriadBean != null){
			propertyValues.addPropertyValue("child",
					new RuntimeBeanReference((String)childTriadBean.getAttribute("id")));
		}
		return beanDefinition;
	}

	private BeanDefinition parseChildTriad(Node node, ParserContext parserContext) {
		NodeList childNodes = node.getChildNodes();
		BeanDefinition childTriadBean = null;
		for (int i = 0; i < childNodes.getLength();++i){
			Node childNode = childNodes.item(i);
			if ("triad".equals(childNode.getLocalName())){
				BeanDefinitionParser parser = new ChildTriadDefinitionParser();
				childTriadBean = parser.parse((Element)childNode, parserContext);
			}
		}
		return childTriadBean;
	}        

	private MutablePropertyValues updateProperties(Node node,
			RootBeanDefinition beanDefinition) {
		MutablePropertyValues propertyValues = beanDefinition
		.getPropertyValues();
		Node attr = null;
		if ((attr = node.getAttributes()
				.getNamedItem("applicationEventRef")) != null) {
			propertyValues.addPropertyValue("applicationEvent",
					new RuntimeBeanReference(attr.getNodeValue()));
		}
		if ((attr = node.getAttributes().getNamedItem("componentEventRef")) != null) {
			propertyValues.addPropertyValue("componentEvent",
					new RuntimeBeanReference(attr.getNodeValue()));
		}
		if ((attr = node.getAttributes().getNamedItem("child")) != null) {
			propertyValues.addPropertyValue("childTriad",
					new RuntimeBeanReference(attr.getNodeValue()));
		}
		if ((attr = node.getAttributes().getNamedItem("ref")) != null) {
			propertyValues.addPropertyValue("child",
					new RuntimeBeanReference(attr.getNodeValue()));
		}
		if ((attr = node.getAttributes().getNamedItem("viewRef")) != null) {
			propertyValues.addPropertyValue("viewContainer",
					new RuntimeBeanReference(attr.getNodeValue()));
		}
		if ((attr = node.getAttributes().getNamedItem("triad")) != null) {
			propertyValues.addPropertyValue("triad",
					new RuntimeBeanReference(attr.getNodeValue()));
		}
		if ((attr = node.getAttributes().getNamedItem(VIEWNAME)) != null) {
			propertyValues.addPropertyValue(VIEWNAME, attr.getNodeValue());
		}
		return propertyValues;
	}
}
